package first.group.ssh.globalstate;

import first.group.ssh.ConnectionCallback;
import first.group.ssh.PacketConnectStage;
import first.group.ssh.packet.GlobalRequestFailurePacket;
import first.group.ssh.packet.GlobalRequestPacket;
import first.group.ssh.packet.GlobalRequestSuccessPacket;
import first.group.ssh.packet.Packet;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GlobalStateManager extends PacketConnectStage implements GlobalStatePacketCallback {

    private static final Logger logger = Logger.getLogger("GlobalStateManager");

    private final ConnectionCallback connectionCallback;

    public GlobalStateManager(ConnectionCallback connectionCallback) {
        this.connectionCallback = connectionCallback;
    }

    @Override
    public void appendPacket(Packet packet) {
        connectionCallback.appendPacket(packet);
    }

    public record RemoteStateRequestResult(boolean succeed, GlobalRequestSuccessPacket packet) {

        public static RemoteStateRequestResult failed() {
            return new RemoteStateRequestResult(false, null);
        }
    }

    public interface RemoteStateRequestListener<T extends GlobalState> {

        public RemoteStateRequestResult onRequested(GlobalStatePacketCallback callback, GlobalState.RequestPacket<T> packet);
    }

    private final LinkedHashMap<Class<? extends GlobalState>, RemoteStateRequestListener<?>> remoteListeners = new LinkedHashMap<>();

    public <T extends GlobalState> void setRemoteRegisterListener(Class<T> clazz, RemoteStateRequestListener<T> listener) {
        remoteListeners.put(clazz, listener);
    }

    public interface LocalStateRequestListener<T extends GlobalState> {

        public void onRequestedSucceed(GlobalStatePacketCallback callback, GlobalState.RequestPacket<T> packet);

        public void onRequestedFailed(GlobalStatePacketCallback callback);
    }

    private static record RequestEntry<T extends GlobalState>(T state, LocalStateRequestListener<T> listener) {

    }

    private final LinkedList<RequestEntry<? extends GlobalState>> localListeners = new LinkedList<>();

    public <T extends GlobalState> void requestState(GlobalState.RequestPacket<T> requestPacket, LocalStateRequestListener<T> listener) {
        var name = requestPacket.getName();
        var wantReply = requestPacket.wantReply;
        if (wantReply) {
            if (listener == null) {
                throw new IllegalArgumentException("Reply wanted but listener is null");
            }
            var packet = new GlobalRequestPacket(name, true, requestPacket);
            localListeners.add(new RequestEntry<>(requestPacket.getState(), listener));
            connectionCallback.appendPacket(packet);
        } else {
            if (listener != null) {
                throw new IllegalArgumentException("Reply not wanted but listener is not null");
            }
            var packet = new GlobalRequestPacket(name, false, requestPacket);
            connectionCallback.appendPacket(packet);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void read(Packet packet) throws IOException {
        if (packet instanceof GlobalRequestPacket requestPacket) {
            GlobalState.RequestPacket statePacket = requestPacket.packet;
            if (statePacket == null) {
                logger.log(Level.INFO, "Server requested unknown global state: {0}", requestPacket.name);
                connectionCallback.appendPacket(new GlobalRequestFailurePacket());
                return;
            }
            var clazz = statePacket.getClass();
            for (var entry : remoteListeners.entrySet()) {
                if (!entry.getKey().equals(clazz)) {
                    continue;
                }
                var listener = (RemoteStateRequestListener<GlobalState>) entry.getValue();
                var result = listener.onRequested(this, statePacket);
                if (!statePacket.wantReply) {
                    return;
                }
                if (result.succeed) {
                    connectionCallback.appendPacket(result.packet);
                } else {
                    connectionCallback.appendPacket(new GlobalRequestFailurePacket());
                }
                return;
            }
            connectionCallback.appendPacket(new GlobalRequestFailurePacket());
        } else if (packet instanceof GlobalRequestSuccessPacket successPacket) {
            if (localListeners.isEmpty()) {
                logger.warning("Received GlobalRequestSuccessPacket without any listener for it");
                return;
            }
            var entry = localListeners.poll();
            var statePacket = GlobalStateParser.parseSuccess(entry.state.getName(), true, successPacket.payload);
            entry.listener().onRequestedSucceed(this, statePacket);
        } else if (packet instanceof GlobalRequestFailurePacket) {
            if (localListeners.isEmpty()) {
                logger.warning("Received GlobalRequestFailurePacket without any listener for it");
                return;
            }
            var entry = localListeners.poll();
            entry.listener().onRequestedFailed(this);
        } else {
            throw new IllegalArgumentException("Unwanted packet: " + packet);
        }
    }

    @Override
    public boolean shouldRead() {
        return true;
    }
}
